/*
 * Copyright (c) 2008, Takashi Ito
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of the authors nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#include "tansu_internal.h"
#include "minunit.h"
#include "tansu_socket.h"
#include "tansu_util.h"

int tests_run = 0;
int assertion = 0;
const char *last_test = "";
int line = 0;

static
const char *test_tansu_url_getport()
{
    uint32_t i;

    struct {
        const char *url;
        uint16_t port;
    } list[] = {
        {"http://192.168.0.1",              0},
        {"http://192.168.0.1/",             0},
        {"http://192.168.0.1:8080/",        8080},
        {"http://192.168.0.1:8080/path",    8080},
        {"http://192.168.0.1:0/path",       0},
        {"http://192.168.0.1:1/path",       1},
        {"http://192.168.0.1:65535/path",   65535},
    };

    for (i = 0; i < TNUMELEM(list); i++) {
        mu_assert("URL port", tansu_url_getport(list[i].url) == list[i].port);
    }

    return 0;
}

static
const char *test_tansu_url_getpath()
{
    uint32_t i;

    struct {
        const char *url;
        const char *path;
    } list[] = {
        {"http://192.168.0.1",              NULL},
        {"http://192.168.0.1/",             "/"},
        {"http://192.168.0.1:8080/",        "/"},
        {"http://192.168.0.1:8080/path",    "/path"},
        {"http://192.168.0.1:0/path",       "/path"}, // invalid port
        {"http://192.168.0.1:1/path",       "/path"},
        {"http://192.168.0.1:65535/path",   "/path"},
        {"http://192.168.0.1:65536/path",   "/path"}, // invalid port
    };

    for (i = 0; i < TNUMELEM(list); i++) {
        const char *result = tansu_url_getpath(list[i].url);
        if (list[i].path)
            mu_assert("URL port", strcmp(list[i].path, result) == 0);
        else
            mu_assert("URL port", list[i].path == result);
    }

    return 0;
}

static
const char *test_tansu_url_isaddr()
{
    uint32_t i;
    tansu_status_t status;
    struct sockaddr_storage ss;

    struct {
        const char *url;
        int family;
        const char *address;
        int ret;
    } list[] = {
        {"http://192.168.0.1",              AF_INET, "192.168.0.1", 1},
        {"http://192.168.0.1/",             AF_INET, "192.168.0.1", 1},
        {"http://192.168.0.1:8080/",        AF_INET, "192.168.0.1", 1},
        {"http://192.168.0.1:8080/path",    AF_INET, "192.168.0.1", 1},
        {"http://192.168.0.1:0/path",       AF_INET, "192.168.0.1", 1},
        {"http://192.168.0.1:1/path",       AF_INET, "192.168.0.1", 1},
        {"http://192.168.0.1:65535/path",   AF_INET, "192.168.0.1", 1},
        {"http://192.168.0.1:65536/path",   AF_INET, "192.168.0.1", 1},

        {"http://192.168.0.1",              AF_INET, "192.168.0.2", 0},
        {"http://192.168.0.1/",             AF_INET, "192.168.0.2", 0},
        {"http://192.168.0.1:8080/",        AF_INET, "192.168.0.2", 0},
        {"http://192.168.0.1:8080/path",    AF_INET, "192.168.0.2", 0},
        {"http://192.168.0.1:0/path",       AF_INET, "192.168.0.2", 0},
        {"http://192.168.0.1:1/path",       AF_INET, "192.168.0.2", 0},
        {"http://192.168.0.1:65535/path",   AF_INET, "192.168.0.2", 0},
        {"http://192.168.0.1:65536/path",   AF_INET, "192.168.0.2", 0},
    };

    for (i = 0; i < TNUMELEM(list); i++) {
        status = tansu_socket_set_addr(list[i].family, &ss, list[i].address, 0);
        mu_assert("tansu_socket_set_addr", TANSU_SUCCESS == status);
        mu_assert("tansu_url_isaddr", tansu_url_isaddr(list[i].url, &ss) == list[i].ret);
    }

    return 0;
}

static
const char *test_tansu_url_parse()
{
    uint32_t i;

    struct {
        const char *url;
        tansu_status_t ret;
        const char *scheme;
        const char *user;
        const char *pass;
        const char *address;
        uint16_t port;
        const char *path;
    } list[] = {
        {"http://192.168.0.1",              TANSU_SUCCESS, "http", NULL, NULL, "192.168.0.1", 0, NULL},
        {"http://192.168.0.1/",             TANSU_SUCCESS, "http", NULL, NULL, "192.168.0.1", 0, "/"},
        {"http://192.168.0.1:8080/",        TANSU_SUCCESS, "http", NULL, NULL, "192.168.0.1", 8080, "/"},
        {"http://192.168.0.1:8080/path",    TANSU_SUCCESS, "http", NULL, NULL, "192.168.0.1", 8080, "/path"},
        {"http://192.168.0.1:0/path",       TANSU_INVALID_FORMAT, NULL, NULL, NULL, NULL, 0, NULL},
        {"http://192.168.0.1:1/path",       TANSU_SUCCESS, "http", NULL, NULL, "192.168.0.1", 1, "/path"},
        {"http://192.168.0.1:65535/path",   TANSU_SUCCESS, "http", NULL, NULL, "192.168.0.1", 65535, "/path"},
        {"http://192.168.0.1:65536/path",   TANSU_INVALID_FORMAT, NULL, NULL, NULL, NULL, 0, NULL},
    };

    for (i = 0; i < TNUMELEM(list); i++) {
        tansu_status_t ret;
        char *scheme;
        char *user;
        char *pass;
        char *address;
        uint16_t port;
        const char *path;
        ret = tansu_url_parse(list[i].url, &scheme, &user, &pass, &address, &port, &path);
        mu_assert("URL parse result", ret == list[i].ret);
        mu_assert("URL scheme", scheme ? strcmp(list[i].scheme, scheme) == 0 : list[i].scheme == scheme);
//printf("address = %s\n", address);
        mu_assert("URL user", user ? strcmp(list[i].user, user) == 0 : list[i].user == user);
        mu_assert("URL pass", pass ? strcmp(list[i].pass, pass) == 0 : list[i].pass == pass);
        mu_assert("URL address", address ? strcmp(list[i].address, address) == 0 : list[i].address == address);
        mu_assert("URL port", list[i].port == port);
//printf("path = %s\n", path);
        mu_assert("URL path", path ? strcmp(list[i].path, path) == 0 : list[i].path == path);
        free(scheme);
        free(user);
        free(pass);
        free(address);
    }

    return 0;
}

static
const char *test_strsep()
{
    uint32_t i;
    char *p;
    char *tok;

    struct {
        char text[32];
        const char *delim;
        const char *token[8];
    } list[] = {
        {"abc:def:geh",     ":",    {"abc", "def", "geh", NULL}},
        {"abc:def;geh",     ":;",   {"abc", "def", "geh", NULL}},
        {"abc: def; geh",   ":; ",  {"abc", "", "def", "", "geh", NULL}},
        {"abc::def:geh",    ":",    {"abc", "", "def", "geh", NULL}},
        {":abc:def:geh",    ":",    {"", "abc", "def", "geh", NULL}},
        {":abc:def:geh:",   ":",    {"", "abc", "def", "geh", "", NULL}},
        {"",                ":",    {"", NULL}},
        {":",               ":",    {"", "", NULL}},
        {"abc",             ":",    {"abc", NULL}},
        {"abc",             "",     {"abc", NULL}},
    };

    for (i = 0; i < TNUMELEM(list); i++) {
        int j = 0;
        p = list[i].text;
        while (p) {
            tok = strsep(&p, list[i].delim);
//printf("[%d-%d] tok(%s), expected(%s)\n", i, j, tok, list[i].token[j] ? list[i].token[j] : "NULL");
            if (tok)
                mu_assert("token check", strcmp(tok, list[i].token[j]) == 0);
            else
                mu_assert("token end check", tok == list[i].token[j]);
            j++;
        }
    }

    //mu_assert("strsep(NULL, \":\")", strsep(NULL, ":") == NULL);

    return 0;
}

static
const char *test_tansu_socket_listen()
{
    int fd;
    int ret;
    tansu_status_t status;

    fd = tansu_socket_listen(AF_INET, SOCK_DGRAM, "0.0.0.0", 55555, 10, NULL);
    mu_assert("new socket", fd != -1);

    struct sockaddr_in addr;
    socklen_t len = sizeof(addr);
    ret = getsockname(fd, (struct sockaddr *)&addr, &len);
    mu_assert("getsockname", ret == 0);
    mu_assert("port", ntohs(addr.sin_port) == 55555);

    status = tansu_socket_close(fd);
    mu_assert("close socket", status == TANSU_SUCCESS);

    return 0;
}

static
const char *all_tests()
{
    mu_run_test(test_tansu_socket_listen);
    mu_run_test(test_tansu_url_getport);
    mu_run_test(test_tansu_url_getpath);
    mu_run_test(test_tansu_url_isaddr);
    mu_run_test(test_tansu_url_parse);
    mu_run_test(test_strsep);
    return 0;
}

#define LOG(...)    tlog(TLOG_LEVEL_ALL, TLOG_ALL, "" __VA_ARGS__)

int main(int argc, char *argv[])
{
    const char *result = all_tests();
    if (result != 0) {
        LOG("Failed: %s:%d\n", last_test, line);
        LOG("%s\n", result);
    } else {
        LOG("ALL TESTS PASSED\n");
        LOG("  Tests run : %d\n", tests_run);
        LOG("  Assertion : %d\n", assertion);
    }

    return result != 0;
}

